#include "stdafx.h"
#include "filesys/path.hpp"
#include "filesys/environment.hpp"
#include "stdlib/exception.hpp"
#include "stdlib/bugcheck.hpp"
using namespace JHCPP::stdlib;
using namespace JHCPP::stdlib::jhException;
#include "stream/stringtokenizer.hpp"
using namespace JHCPP::stream;

#include "platform.hpp"
#include <algorithm>
#if defined(JH_OS_WINDOWS)
#include <Windows.h>
#endif

namespace JHCPP
{
	namespace filesys
	{
		CPath::CPath(): m_absolute(false)
		{
		}

		CPath::CPath(bool absolute): m_absolute(absolute)
		{
		}

		CPath::CPath(const std::string& path)
		{
			assign(path);
		}

		CPath::CPath(const std::string& path, Style style)
		{
			assign(path, style);
		}

		CPath::CPath(const char* path)
		{
			jh_check_ptr(path);
			assign(path);
		}

		CPath::CPath(const char* path, Style style)
		{
			jh_check_ptr(path);
			assign(path, style);
		}

		CPath::CPath(const CPath& path)
		: m_node(path.m_node), 
		m_device(path.m_device),
		m_name(path.m_name),
		m_version(path.m_version),
		m_dirs(path.m_dirs),
		m_absolute(path.m_absolute)
		{	
		}

		CPath::CPath(const CPath& parent, const std::string& fileName)
		:m_node(parent.m_node), 
		m_device(parent.m_device),
		m_name(parent.m_name),
		m_version(parent.m_version),
		m_dirs(parent.m_dirs),
		m_absolute(parent.m_absolute)
		{	
			makeDirectory();
			m_name = fileName;
		}

		CPath::CPath(const CPath& parent, const char* fileName)
		:m_node(parent.m_node), 
		m_device(parent.m_device),
		m_name(parent.m_name),
		m_version(parent.m_version),
		m_dirs(parent.m_dirs),
		m_absolute(parent.m_absolute)
		{	
			makeDirectory();
			m_name = fileName;
		}

		CPath::CPath(const CPath& parent, const CPath& relative)
		:m_node(parent.m_node), 
		m_device(parent.m_device),
		m_name(parent.m_name),
		m_version(parent.m_version),
		m_dirs(parent.m_dirs),
		m_absolute(parent.m_absolute)
		{	
			resolve(relative);
		}

		CPath::~CPath()
		{
		}

		CPath& CPath::operator = (const CPath& path)
		{
			return assign(path);
		}

		CPath& CPath::operator = (const std::string& path)
		{
			return assign(path);
		}

		CPath& CPath::operator = (const char* path)
		{
			jh_check_ptr(path);
			return assign(path);
		}

		void CPath::swap(CPath& path)
		{
			std::swap(m_node, path.m_node);
			std::swap(m_device, path.m_device);
			std::swap(m_name, path.m_name);
			std::swap(m_version, path.m_version);
			std::swap(m_dirs, path.m_dirs);
			std::swap(m_absolute, path.m_absolute);
		}

		CPath& CPath::assign(const std::string& path)
		{
		#if defined(JH_OS_WINDOWS)
			parseWindows(path);
		#else
			parseLinux(path);
		#endif
			return *this;
		}

		CPath& CPath::parse(const std::string& path)
		{
			return assign(path);
		}

		CPath& CPath::assign(const std::string& path, Style style)
		{
			switch (style)
			{
			case PATH_LUNIX:
				parseLinux(path);
				break;
			case PATH_WINDOWS:
				parseWindows(path);
				break;
			default:
				jh_bugcheck();
				break;
			}
			return *this;
		}

		CPath& CPath::parse(const std::string& path, Style style)
		{
			return assign(path, style);
		}

		CPath& CPath::assign(const char* path)
		{
			return assign(std::string(path));
		}

		CPath& CPath::assign(const CPath& path)
		{
			if (&path != this)
			{
				m_node     = path.m_node;
				m_device   = path.m_device;
				m_name     = path.m_name;
				m_version  = path.m_version;
				m_dirs     = path.m_dirs;
				m_absolute = path.m_absolute;
			}
			return *this;
		}

		std::string CPath::toString() const
		{
		#if defined(JH_OS_LINUX)
			return buildLinux();
		#elif defined(JH_OS_WINDOWS)
			return buildWindows();
		#endif
			return std::string();
		}

		std::string CPath::toString(Style style) const
		{
			switch (style)
			{
			case PATH_LUNIX:
				return buildLinux();
			case PATH_WINDOWS:
				return buildWindows();
			default:
				jh_bugcheck();
				break;
			}
			return std::string();
		}

		bool CPath::tryParse(const std::string& path)
		{
			try
			{
				CPath p;
				p.parse(path);
				assign(p);
				return true;
			}
			catch (...)
			{
				return false;
			}
		}

		bool CPath::tryParse(const std::string& path, Style style)
		{
			try
			{
				CPath p;
				p.parse(path, style);
				assign(p);
				return true;
			}
			catch (...)
			{
				return false;
			}
		}

		CPath& CPath::parseDirectory(const std::string& path)
		{
			assign(path);
			return makeDirectory();
		}

		CPath& CPath::parseDirectory(const std::string& path, Style style)
		{
			assign(path, style);
			return makeDirectory();
		}

		CPath& CPath::makeDirectory()
		{
			pushDirectory(m_name);
			m_name.clear();
			m_version.clear();
			return *this;
		}

		CPath& CPath::makeFile()
		{
			if (!m_dirs.empty() && m_name.empty())
			{
				m_name = m_dirs.back();
				m_dirs.pop_back();
			}
			return *this;
		}

		CPath& CPath::makeParent()
		{
			if (m_name.empty())
			{
				if (m_dirs.empty())
				{
					if (!m_absolute)
						m_dirs.push_back("..");
				}
				else
				{
					if (m_dirs.back() == "..")
						m_dirs.push_back("..");
					else
						m_dirs.pop_back();
				}
			}
			else
			{
				m_name.clear();
				m_version.clear();
			}
			return *this;
		}

		CPath& CPath::makeAbsolute()
		{
			return makeAbsolute(current());
		}

		CPath& CPath::makeAbsolute(const CPath& base)
		{
			if (!m_absolute)
			{
				CPath tmp = base;
				tmp.makeDirectory();
				for (StringVec::const_iterator it = m_dirs.begin(); it != m_dirs.end(); ++it)
				{
					tmp.pushDirectory(*it);
				}
				m_node     = tmp.m_node;
				m_device   = tmp.m_device;
				m_dirs     = tmp.m_dirs;
				m_absolute = base.m_absolute;
			}
			return *this;
		}

		CPath& CPath::append(const CPath& path)
		{
			makeDirectory();
			m_dirs.insert(m_dirs.end(), path.m_dirs.begin(), path.m_dirs.end());
			m_name = path.m_name;
			m_version = path.m_version;
			return *this;
		}

		CPath& CPath::resolve(const CPath& path)
		{
			if (path.isAbsolute())
			{
				assign(path);
			}
			else
			{
				for (int i = 0; i < path.depth(); ++i)
					pushDirectory(path[i]);
				m_name = path.m_name;
			}
			return *this;
		}

		bool CPath::isAbsolute() const
		{
			return m_absolute;
		}

		bool CPath::isRelative() const
		{
			return !m_absolute;
		}

		bool CPath::isDirectory() const
		{
			return m_name.empty();
		}

		bool CPath::isFile() const
		{
			return !m_name.empty();
		}

		CPath& CPath::setNode(const std::string& node)
		{
			m_node     = node;
			m_absolute = m_absolute || !node.empty();
			return *this;
		}

		const std::string& CPath::getNode() const
		{
			return m_node;
		}

		CPath& CPath::setDevice(const std::string& device)
		{
			m_device   = device;
			m_absolute = m_absolute || !device.empty();
			return *this;
		}

		const std::string& CPath::getDevice() const
		{
			return m_device;
		}

		int CPath::depth() const
		{
			return int(m_dirs.size());
		}

		const std::string& CPath::directory(int n) const
		{
			jh_assert (0 <= n && n <= (int)m_dirs.size());

			if (n < (int)m_dirs.size())
				return m_dirs[n];
			else
				return m_name;	
		}

		const std::string& CPath::operator [] (int n) const
		{
			jh_assert (0 <= n && n <= (int)m_dirs.size());

			if (n < (int)m_dirs.size())
				return m_dirs[n];
			else
				return m_name;	
		}

		CPath& CPath::pushDirectory(const std::string& dir)
		{
			if (!dir.empty() && dir != ".")
			{
				if (dir == "..")
				{
					if (!m_dirs.empty() && m_dirs.back() != "..")
						m_dirs.pop_back();
					else if (!m_absolute)
						m_dirs.push_back(dir);
				}
				else m_dirs.push_back(dir);
			}
			return *this;
		}

		CPath& CPath::popDirectory()
		{
			jh_assert (!m_dirs.empty());

			m_dirs.pop_back();
			return *this;
		}

		CPath& CPath::popFrontDirectory()
		{
			jh_assert (!m_dirs.empty());

			StringVec::iterator it = m_dirs.begin();
			m_dirs.erase(it);
			return *this;
		}

		CPath& CPath::setFileName(const std::string& name)
		{
			m_name = name;
			return *this;
		}

		const std::string& CPath::getFileName() const
		{
			return m_name;
		}

		CPath& CPath::setBaseName(const std::string& name)
		{
			std::string ext = getExtension();
			m_name = name;
			if (!ext.empty())
			{
				m_name.append(".");
				m_name.append(ext);
			}
			return *this;
		}

		std::string CPath::getBaseName() const
		{
			std::string::size_type pos = m_name.rfind('.');
			if (pos != std::string::npos)
				return m_name.substr(0, pos);
			else
				return m_name;
		}

		CPath& CPath::setExtension(const std::string& extension)
		{
			m_name = getBaseName();
			if (!extension.empty())
			{
				m_name.append(".");
				m_name.append(extension);
			}
			return *this;
		}

		std::string CPath::getExtension() const
		{
			std::string::size_type pos = m_name.rfind('.');
			if (pos != std::string::npos)
				return m_name.substr(pos + 1);
			else
				return std::string();
		}

		CPath& CPath::clear()
		{
			m_node.clear();
			m_device.clear();
			m_name.clear();
			m_dirs.clear();
			m_version.clear();
			m_absolute = false;
			return *this;
		}

		CPath CPath::parent() const
		{
			CPath p(*this);
			return p.makeParent();
		}

		CPath CPath::absolute() const
		{
			CPath result(*this);
			if (!result.m_absolute)
			{
				result.makeAbsolute();
			}
			return result;
		}

		CPath CPath::absolute(const CPath& base) const
		{
			CPath result(*this);
			if (!result.m_absolute)
			{
				result.makeAbsolute(base);
			}
			return result;
		}

		CPath CPath::forDirectory(const std::string& path)
		{
			CPath p;
			return p.parseDirectory(path);
		}

		CPath CPath::forDirectory(const std::string& path, Style style)
		{
			CPath p;
			return p.parseDirectory(path, style);
		}

		char CPath::separator()
		{
		#if defined(JH_OS_WINDOWS)
			return '\\';
		#elif defined(JH_OS_LINUX)
			return '/';
		#endif
			return 0;
		}

		char CPath::pathSeparator()
		{
		#if defined(JH_OS_WINDOWS)
			return ';';
		#elif defined(JH_OS_LINUX)
			return ':';
		#endif
			return 0;
		}

		std::string CPath::current()
		{
		#ifdef JH_OS_WINDOWS
			#ifdef _UNICODE
			#else
				char buffer[MAX_PATH] = {0};
				DWORD n = GetCurrentDirectory(sizeof(buffer), buffer);
				if (n > 0 && n < sizeof(buffer))
				{
					std::string result(buffer, n);
					if (result[n - 1] != '\\')
						result.append("\\");
					return result;
				}
				else throw jhException::SystemException("Cannot get current directory");
			#endif
		#elif defined(JH_OS_LINUX)

		#endif
		}

		std::string CPath::home()
		{
		#ifdef JH_OS_WINDOWS
			#ifdef _UNICODE
			#else
				std::string result;
				try
				{
					// windows service has no home dir, return system directory instead
					result = CEnvironment::get("HOMEDRIVE");
					result.append(CEnvironment::get("HOMEPATH"));
				}
				catch (NotFoundException&) 
				{
					char buffer[MAX_PATH];
					DWORD n = ::GetSystemDirectory(buffer, sizeof(buffer));
					if (n > 0 && n < sizeof(buffer))
					{
						std::string result(buffer, n);
						if (result[n - 1] != '\\')
							result.append("\\");
						return result;
					}
					else throw SystemException("Cannot get system directory");
				}

				std::string::size_type n = result.size();
				if (n > 0 && result[n - 1] != '\\')
					result.append("\\");
				return result;
			#endif
		#endif
		}

		std::string CPath::temp()
		{
		#ifdef JH_OS_WINDOWS
			#ifdef _UNICODE
			#else
				char buffer[MAX_PATH];
				DWORD n = ::GetTempPath(sizeof(buffer), buffer);
				if (n > 0 && n < sizeof(buffer))
				{
					n = ::GetLongPathName(buffer, buffer, static_cast<DWORD>(sizeof buffer));
					if (n <= 0) throw SystemException("Cannot get temporary directory long path name");
					std::string result(buffer, n);
					if (result[n - 1] != '\\')
						result.append("\\");
					return result;
				}
				else throw SystemException("Cannot get temporary directory");
			#endif
		#endif
			return std::string();
		}

		std::string CPath::null()
		{
		#ifdef JH_OS_WINDOWS
			#ifdef _UNICODE
			#else
				return "NUL:";
			#endif
		#endif
		}

		std::string CPath::expand(const std::string& path)
		{
		#ifdef JH_OS_WINDOWS
			#ifdef _UNICODE
			#else
				char buffer[MAX_PATH];
				DWORD n = ::ExpandEnvironmentStrings(path.c_str(), buffer, sizeof(buffer));
				if (n > 0 && n < sizeof(buffer))
					return std::string(buffer, n - 1);
				else
					return path;
			#endif
		#endif
			return std::string();
		}

		void CPath::listRoots(std::vector<std::string>& roots)
		{
		#ifdef JH_OS_WINDOWS
			#ifdef _UNICODE
			#else
				roots.clear();
				char buffer[128];
				DWORD n = ::GetLogicalDriveStrings(sizeof(buffer) - 1, buffer);
				char* it = buffer;
				char* end = buffer + (n > sizeof(buffer) ? sizeof(buffer) : n);
				while (it < end)
				{
					std::string dev;
					while (it < end && *it) dev += *it++;
					roots.push_back(dev);
					++it;
				}	
			#endif
		#endif
		}

		bool CPath::find(StringVec::const_iterator it, StringVec::const_iterator end, const std::string& name, CPath& path)
		{
			while (it != end)
			{
			#if defined(WIN32)
				std::string cleanPath(*it);
				if (cleanPath.size() > 1 && cleanPath[0] == '"' && cleanPath[cleanPath.size() - 1] == '"')
				{
					cleanPath = cleanPath.substr(1, cleanPath.size() - 2);
				}
				CPath p(cleanPath);
			#else
				CPath p(*it);
			#endif
				p.makeDirectory();
				p.resolve(CPath(name));
				/*CFile f(p);
				if (f.exists())
				{
					path = p;
					return true;
				}*/
				++it;
			}
			return false;
		}

		bool CPath::find(const std::string& pathList, const std::string& name, CPath& path)
		{
			CStringTokenizer st(pathList, std::string(1, pathSeparator()), CStringTokenizer::TOK_IGNORE_EMPTY + CStringTokenizer::TOK_TRIM);
			return find(st.begin(), st.end(), name, path);
			return false;
		}

		std::string CPath::transcode(const std::string& path)
		{
		#if defined(_WIN32)
			/*std::wstring uniPath;
			UnicodeConverter::toUTF16(path, uniPath);
			DWORD len = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, uniPath.c_str(), static_cast<int>(uniPath.length()), NULL, 0, NULL, NULL);
			if (len > 0)
			{
				Buffer<char> buffer(len);
				DWORD rc = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, uniPath.c_str(), static_cast<int>(uniPath.length()), buffer.begin(), static_cast<int>(buffer.size()), NULL, NULL);
				if (rc)
				{
					return std::string(buffer.begin(), buffer.size());
				}
			}*/
		#endif
			return path;
		}

		void CPath::parseLinux(const std::string& path)
		{
			clear();

			std::string::const_iterator it  = path.begin();
			std::string::const_iterator end = path.end();

			if (it != end)
			{
				if (*it == '/') 
				{
					m_absolute = true; ++it;
				}
				else if (*it == '~')
				{
					++it;
					if (it == end || *it == '/')
					{
						CPath cwd(home());
						m_dirs = cwd.m_dirs;
						m_absolute = true;
					}
					else --it;
				}

				while (it != end)
				{
					std::string name;
					while (it != end && *it != '/') name += *it++;
					if (it != end)
					{
						if (m_dirs.empty())
						{
							if (!name.empty() && *(name.rbegin()) == ':')
							{
								m_absolute = true;
								m_device.assign(name, 0, name.length() - 1);
							}
							else
							{
								pushDirectory(name);
							}
						}
						else pushDirectory(name);
					}
					else m_name = name;
					if (it != end) ++it;
				}
			}
		}

		void CPath::parseWindows(const std::string& path)
		{
			clear();

			std::string::const_iterator it  = path.begin();
			std::string::const_iterator end = path.end();

			if (it != end)
			{
				if (*it == '\\' || *it == '/') { m_absolute = true; ++it; }
				if (m_absolute && it != end && (*it == '\\' || *it == '/')) // UNC
				{
					++it;
					while (it != end && *it != '\\' && *it != '/') m_node += *it++;
					if (it != end) ++it;
				}
				else if (it != end)
				{
					char d = *it++;
					if (it != end && *it == ':') // drive letter
					{
						if (m_absolute || !((d >= 'a' && d <= 'z') || (d >= 'A' && d <= 'Z'))) throw PathSyntaxException(path);
						m_absolute = true;
						m_device += d;
						++it;
						if (it == end || (*it != '\\' && *it != '/')) throw PathSyntaxException(path);
						++it;
					}
					else --it;
				}
				while (it != end)
				{
					std::string name;
					while (it != end && *it != '\\' && *it != '/') name += *it++;
					if (it != end)
						pushDirectory(name);
					else
						m_name = name;
					if (it != end) ++it;
				}
			}
			if (!m_node.empty() && m_dirs.empty() && !m_name.empty())
				makeDirectory();
		}

		std::string CPath::buildLinux() const
		{
			std::string result;
			if (!m_device.empty())
			{
				result.append("/");
				result.append(m_device);
				result.append(":/");
			}
			else if (m_absolute)
			{
				result.append("/");
			}
			for (StringVec::const_iterator it = m_dirs.begin(); it != m_dirs.end(); ++it)
			{
				result.append(*it);
				result.append("/");
			}
			result.append(m_name);
			return result;
		}


		std::string CPath::buildWindows() const
		{
			std::string result;
			if (!m_node.empty())
			{
				result.append("\\\\");
				result.append(m_node);
				result.append("\\");
			}
			else if (!m_device.empty())
			{
				result.append(m_device);
				result.append(":\\");
			}
			else if (m_absolute)
			{
				result.append("\\");
			}
			for (StringVec::const_iterator it = m_dirs.begin(); it != m_dirs.end(); ++it)
			{
				result.append(*it);
				result.append("\\");
			}
			result.append(m_name);
			return result;
		}

	}//end of namespace filesys
}//end of namespace JHCPP